package iusers

import (
	"bytes"
	"context"
	"crypto/rand"
	"crypto/sha1"
	"fmt"

	"gitee.com/bitwormhole/weixin-applet-backend/app/classes/wxusers"
	"gitee.com/bitwormhole/weixin-applet-backend/app/data/dxo"
	"gitee.com/bitwormhole/weixin-applet-backend/app/data/entity"
	"github.com/starter-go/base/lang"
	"github.com/starter-go/rbac"
	"github.com/starter-go/security-gorm/rbacdb"
	"gorm.io/gorm"
)

// WeixinUserServiceImpl ...
type WeixinUserServiceImpl struct {

	//starter:component

	_as func(wxusers.Service) //starter:as("#")

	Agent       dxo.DatabaseAgent //starter:inject("#")
	WxUserDAO   wxusers.DAO       //starter:inject("#")
	UserDAO     rbacdb.UserDAO    //starter:inject("#")
	UserService rbac.UserService  //starter:inject("#")

}

func (inst *WeixinUserServiceImpl) _impl() wxusers.Service {
	return inst
}

// Find ...
func (inst *WeixinUserServiceImpl) Find(c context.Context, id wxusers.ID) (*wxusers.DTO, error) {

	o1, err := inst.WxUserDAO.Find(nil, id)
	if err != nil {
		return nil, err
	}

	o2 := wxusers.ConvertE2D(o1)
	return o2, nil
}

// FindByOpenID ...
func (inst *WeixinUserServiceImpl) FindByOpenID(c context.Context, openid string) (*wxusers.DTO, error) {

	o1, err := inst.WxUserDAO.FindByOpenID(nil, openid)
	if err != nil {
		return nil, err
	}
	o2 := wxusers.ConvertE2D(o1)
	return o2, nil
}

// HasOpenID ...
func (inst *WeixinUserServiceImpl) HasOpenID(c context.Context, openid string) bool {

	has, err := inst.WxUserDAO.ContainOpenID(nil, openid)
	if err != nil {
		return false
	}
	return has
}

// SignUp ...
func (inst *WeixinUserServiceImpl) SignUp(c context.Context, item *wxusers.DTO) (*wxusers.DTO, error) {
	db := inst.Agent.DB(nil)
	o2 := wxusers.ConvertD2E(item)
	err := db.Transaction(func(tx *gorm.DB) error {
		return inst.innerSignUp(tx, o2)
	})
	if err != nil {
		return nil, err
	}
	id := o2.ID
	return inst.Find(c, id)
}

func (inst *WeixinUserServiceImpl) prepareNamesForSignUp(user1 *rbacdb.UserEntity, user2 *entity.WeixinAppletUser) {

	// generate name
	openid := user2.OpenID
	nonce := make([]byte, 32)
	rand.Read(nonce)

	builder := &bytes.Buffer{}
	builder.Write(nonce)
	builder.WriteString(openid)
	sum := sha1.Sum(builder.Bytes())
	hex := lang.HexFromBytes(sum[:])
	hash := hex.String()

	username := "wx-user-" + hash[0:10]
	nickname := "User" + hash[0:10]

	user1.Name = rbac.UserName(username)
	user1.Nickname = nickname
	user1.Salt = hex
}

func (inst *WeixinUserServiceImpl) innerSignUp(db *gorm.DB, wxUser *entity.WeixinAppletUser) error {

	// insert rbac.user
	user1 := &rbacdb.UserEntity{
		Name:     "todo",
		Nickname: "todo",
		Roles:    "user",
		Avatar:   "/images/avatars/a1.png",
		Enabled:  true,
	}

	inst.prepareNamesForSignUp(user1, wxUser)

	user2, err := inst.UserDAO.Insert(db, user1)
	if err != nil {
		return err
	}

	uid := user2.ID

	// insert weixin-user
	wxUser.Creator = uid
	wxUser.Owner = uid
	wxUser.Updater = uid

	_, err = inst.WxUserDAO.Insert(db, wxUser)
	return err
}

// SignIn ...
func (inst *WeixinUserServiceImpl) SignIn(c context.Context, item *wxusers.DTO) (*rbac.UserDTO, error) {

	o2 := wxusers.ConvertD2E(item)
	wxuid := o2.ID
	if wxuid < 1 {
		return nil, fmt.Errorf("bad_wx_user_id:%d", wxuid)
	}

	wxUser, err := inst.WxUserDAO.Find(nil, wxuid)
	if err != nil {
		return nil, err
	}

	return inst.UserService.Find(nil, wxUser.Owner)
}
